﻿#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

#if !(NET35 || NET20 || PORTABLE40)
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
#if !(PORTABLE || WINDOWS_PHONE)
using System.Reflection;
#else
using Microsoft.CSharp.RuntimeBinder;
#endif
using System.Runtime.CompilerServices;
using System.Text;
using System.Globalization;
using DreamCube.Foundation.Serialization.Serialization;

namespace DreamCube.Foundation.Serialization.Utilities
{
  internal static class DynamicUtils
  {
    internal static class BinderWrapper
    {
#if !(PORTABLE || WINDOWS_PHONE)
#if !SILVERLIGHT
      public const string CSharpAssemblyName = "Microsoft.CSharp, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
#else
      public const string CSharpAssemblyName = "Microsoft.CSharp, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
#endif

      private const string BinderTypeName = "Microsoft.CSharp.RuntimeBinder.Binder, " + CSharpAssemblyName;
      private const string CSharpArgumentInfoTypeName = "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo, " + CSharpAssemblyName;
      private const string CSharpArgumentInfoFlagsTypeName = "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, " + CSharpAssemblyName;
      private const string CSharpBinderFlagsTypeName = "Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, " + CSharpAssemblyName;

      private static object _getCSharpArgumentInfoArray;
      private static object _setCSharpArgumentInfoArray;
      private static MethodCall<object, object> _getMemberCall;
      private static MethodCall<object, object> _setMemberCall;
      private static bool _init;

      private static void Init()
      {
        if (!_init)
        {
          Type binderType = Type.GetType(BinderTypeName, false);
          if (binderType == null)
            throw new InvalidOperationException("Could not resolve type '{0}'. You may need to add a reference to Microsoft.CSharp.dll to work with dynamic types.".FormatWith(CultureInfo.InvariantCulture, BinderTypeName));

          // None
          _getCSharpArgumentInfoArray = CreateSharpArgumentInfoArray(0);
          // None, Constant | UseCompileTimeType
          _setCSharpArgumentInfoArray = CreateSharpArgumentInfoArray(0, 3);
          CreateMemberCalls();

          _init = true;
        }
      }

      private static object CreateSharpArgumentInfoArray(params int[] values)
      {
        Type csharpArgumentInfoType = Type.GetType(CSharpArgumentInfoTypeName);
        Type csharpArgumentInfoFlags = Type.GetType(CSharpArgumentInfoFlagsTypeName);

        Array a = Array.CreateInstance(csharpArgumentInfoType, values.Length);

        for (int i = 0; i < values.Length; i++)
        {
          MethodInfo createArgumentInfoMethod = csharpArgumentInfoType.GetMethod("Create", new[] {csharpArgumentInfoFlags, typeof (string)});
          object arg = createArgumentInfoMethod.Invoke(null, new object[] {0, null});
          a.SetValue(arg, i);
        }

        return a;
      }

      private static void CreateMemberCalls()
      {
        Type csharpArgumentInfoType = Type.GetType(CSharpArgumentInfoTypeName, true);
        Type csharpBinderFlagsType = Type.GetType(CSharpBinderFlagsTypeName, true);
        Type binderType = Type.GetType(BinderTypeName, true);

        Type csharpArgumentInfoTypeEnumerableType = typeof (IEnumerable<>).MakeGenericType(csharpArgumentInfoType);

        MethodInfo getMemberMethod = binderType.GetMethod("GetMember", new[] {csharpBinderFlagsType, typeof (string), typeof (Type), csharpArgumentInfoTypeEnumerableType});
        _getMemberCall = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(getMemberMethod);

        MethodInfo setMemberMethod = binderType.GetMethod("SetMember", new[] {csharpBinderFlagsType, typeof (string), typeof (Type), csharpArgumentInfoTypeEnumerableType});
        _setMemberCall = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(setMemberMethod);
      }
#endif

      public static CallSiteBinder GetMember(string name, Type context)
      {
#if !(PORTABLE || WINDOWS_PHONE)
        Init();
        return (CallSiteBinder) _getMemberCall(null, 0, name, context, _getCSharpArgumentInfoArray);
#else
        return Binder.GetMember(
          CSharpBinderFlags.None, name, context, new[] {CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)});
#endif
      }

      public static CallSiteBinder SetMember(string name, Type context)
      {
#if !(PORTABLE || WINDOWS_PHONE)
        Init();
        return (CallSiteBinder) _setMemberCall(null, 0, name, context, _setCSharpArgumentInfoArray);
#else
        return Binder.SetMember(
          CSharpBinderFlags.None, name, context, new[]
                                                   {
                                                     CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null),
                                                     CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant, null)
                                                   });
#endif
      }
    }

    public static IEnumerable<string> GetDynamicMemberNames(this IDynamicMetaObjectProvider dynamicProvider)
    {
      DynamicMetaObject metaObject = dynamicProvider.GetMetaObject(Expression.Constant(dynamicProvider));
      return metaObject.GetDynamicMemberNames();
    }
  }

  internal class NoThrowGetBinderMember : GetMemberBinder
  {
    private readonly GetMemberBinder _innerBinder;

    public NoThrowGetBinderMember(GetMemberBinder innerBinder)
      : base(innerBinder.Name, innerBinder.IgnoreCase)
    {
      _innerBinder = innerBinder;
    }

    public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
    {
      DynamicMetaObject retMetaObject = _innerBinder.Bind(target, new DynamicMetaObject[] { });

      NoThrowExpressionVisitor noThrowVisitor = new NoThrowExpressionVisitor();
      Expression resultExpression = noThrowVisitor.Visit(retMetaObject.Expression);

      DynamicMetaObject finalMetaObject = new DynamicMetaObject(resultExpression, retMetaObject.Restrictions);
      return finalMetaObject;
    }
  }

  internal class NoThrowSetBinderMember : SetMemberBinder
  {
    private readonly SetMemberBinder _innerBinder;

    public NoThrowSetBinderMember(SetMemberBinder innerBinder)
      : base(innerBinder.Name, innerBinder.IgnoreCase)
    {
      _innerBinder = innerBinder;
    }

    public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, DynamicMetaObject value, DynamicMetaObject errorSuggestion)
    {
      DynamicMetaObject retMetaObject = _innerBinder.Bind(target, new DynamicMetaObject[] { value });

      NoThrowExpressionVisitor noThrowVisitor = new NoThrowExpressionVisitor();
      Expression resultExpression = noThrowVisitor.Visit(retMetaObject.Expression);

      DynamicMetaObject finalMetaObject = new DynamicMetaObject(resultExpression, retMetaObject.Restrictions);
      return finalMetaObject;
    }
  }

  internal class NoThrowExpressionVisitor : ExpressionVisitor
  {
    internal static readonly object ErrorResult = new object();

    protected override Expression VisitConditional(ConditionalExpression node)
    {
      // if the result of a test is to throw an error, rewrite to result an error result value
      if (node.IfFalse.NodeType == ExpressionType.Throw)
        return Expression.Condition(node.Test, node.IfTrue, Expression.Constant(ErrorResult));

      return base.VisitConditional(node);
    }
  }

}
#endif